Schemify
Testing

Testing

Test Structure

src/
  *.zig           unit tests inline (test "..." blocks)
tests/
  roundtrip/      xschem ↔ CHN roundtrip tests
  netlist/        SPICE netlist generation tests
  fixtures/       test schematics (.chn, .sch, .spice)
plugins/
  EasyImport/
    test/         EasyImport plugin integration tests

Running Tests

# All tests
zig build test

# Filter by name
zig build test -- --test-filter "CHN parser"

# Filter by file
zig build test -- src/core/chn/parser.zig

# Verbose output
zig build test -- --verbose

Unit Tests

Unit tests live inline in the source file they test:

// src/core/chn/parser.zig

pub fn parseChN(allocator: std.mem.Allocator, input: []const u8) !Schematic {
    // ...
}

test "parseChN: simple SYMBOL section" {
    const input =
        \\chn 1
        \\SYMBOL inv
        \\  pins:
        \\    A  in  x=0 y=0
        \\    Z  out x=40 y=0
    ;
    const result = try parseChN(std.testing.allocator, input);
    defer result.deinit(std.testing.allocator);

    const sym = result.symbol orelse return error.NoSymbol;
    try std.testing.expectEqualStrings("inv", sym.name);
    try std.testing.expectEqual(@as(usize, 2), sym.pins.len);
}

Roundtrip Tests

The xschem roundtrip tests verify that reading a .sch file and converting to .chn and back produces an equivalent schematic:

tests/roundtrip/
  fixtures/
    cmos_inv.sch      xschem source
    cmos_inv.chn      expected CHN output
  roundtrip_test.zig
zig build test -- --test-filter "roundtrip"

The roundtrip tests use a dependency graph to handle fixture ordering — schematics that reference symbols are tested after the symbols they depend on.

Integration Tests

Integration tests in tests/ use real file I/O via std.testing.tmpDir():

test "FileIO: write CHN and read back" {
    var tmp = std.testing.tmpDir(.{});
    defer tmp.cleanup();

    const path = try tmp.dir.realpathAlloc(std.testing.allocator, "test.chn");
    defer std.testing.allocator.free(path);

    const schematic = try buildTestSchematic();
    try FileIO.writeChn(schematic, path);

    const result = try FileIO.readChn(std.testing.allocator, path);
    defer result.deinit(std.testing.allocator);

    try std.testing.expectEqual(schematic.instances.len, result.instances.len);
}

GUI Tests

The GUI test checklist in TODO.md covers 30+ categories of manual tests for the editor. These are not automated — they require running the editor and verifying behavior visually.

Key categories:

  • Wire placement and deletion
  • Instance placement, rotation, flip
  • Rubber-band selection
  • Undo/redo across all operations
  • Zoom and pan
  • Symbol browser
  • Net label placement and connectivity
  • Plugin panel docking
  • Schematic saving and loading

Plugin Tests

Plugin integration tests build and load the plugin into a test host:

test "EasyImport: convert xschem inverter" {
    const plugin = try PluginHost.loadPlugin("libEasyImport.so");
    defer plugin.unload();

    // Send load message
    const load_result = plugin.process(.{.load = {}});
    try std.testing.expect(load_result.registered_panels.len > 0);

    // Send convert command
    const convert_result = plugin.process(.{
        .command = .{ .tag = "convert", .payload = "fixtures/cmos_inv.sch" }
    });
    try std.testing.expect(convert_result.vfs_writes.len > 0);
}

Test Conventions

  • No mocking. Use real data structures, real parsers.
  • For file I/O tests: std.testing.tmpDir()
  • For schematic tests: use tests/fixtures/ test schematics
  • All public functions must have at least one test
  • Test names follow: "ModuleName: description of scenario"